﻿using System;
using UnityEngine;

namespace Obi
{
	[ExecuteInEditMode]
	[RequireComponent(typeof(ObiCollider))]
	public class ObiDistanceFieldRenderer : MonoBehaviour
	{
		public enum Axis{
			X = 0,
			Y = 1,
			Z = 2,
		}

		public Axis axis;
		[Range(0,1)]
		public float slice = 0.25f;
		public float maxDistance = 0.5f;

		private ObiCollider unityCollider;
		private Material material;
		private Mesh planeMesh;
		private Texture2D cutawayTexture;

		private float sampleSize;
		private int sampleCount;

		private Color boundsColor = new Color(1,1,1,0.5f);

		public void Awake(){
			unityCollider = GetComponent<ObiCollider>();
		}

		public void OnEnable(){
			material = GameObject.Instantiate(Resources.Load<Material>("ObiMaterials/DistanceFieldRendering"));
			material.hideFlags = HideFlags.HideAndDontSave;
		}

		public void OnDisable(){
			Cleanup();
		}

		private void Cleanup(){
			GameObject.DestroyImmediate(cutawayTexture);
			GameObject.DestroyImmediate(planeMesh);
			GameObject.DestroyImmediate(material);
		}

		private void ResizeTexture(){

			if (cutawayTexture == null){
				cutawayTexture = new Texture2D(sampleCount,sampleCount,TextureFormat.RHalf,false);
				cutawayTexture.wrapMode = TextureWrapMode.Clamp;
				cutawayTexture.hideFlags = HideFlags.HideAndDontSave;
			}else
				cutawayTexture.Resize(sampleCount,sampleCount);
		}

		private void CreatePlaneMesh(ObiDistanceField field){
	
			if (field != null && planeMesh == null){

				float uvBorder = (1-field.FieldBounds.size[0]/(sampleSize*sampleCount)) * 0.5f;

				planeMesh = new Mesh();

				planeMesh.vertices = new Vector3[]{new Vector3(-0.5f,-0.5f,0),
												   new Vector3(0.5f,-0.5f,0),
												   new Vector3(-0.5f,0.5f,0),
												   new Vector3(0.5f,0.5f,0)};

				planeMesh.uv = new Vector2[]{new Vector2(uvBorder,uvBorder),
											 new Vector2(1-uvBorder,uvBorder),
										     new Vector2(uvBorder,1-uvBorder),
											 new Vector2(1-uvBorder,1-uvBorder)};

				planeMesh.normals = new Vector3[]{-Vector3.forward,-Vector3.forward,-Vector3.forward,-Vector3.forward};
				planeMesh.triangles = new int[]{0,2,1,2,3,1};
			}
		}

		private void RefreshCutawayTexture (ObiDistanceField field)
		{
			if (field == null)
				return;

			Bounds b = field.FieldBounds;
			sampleSize = field.EffectiveSampleSize;
			sampleCount = (int)(b.size[0] / sampleSize)+1;

			CreatePlaneMesh(field);
			ResizeTexture();

			float sweep = (sampleCount*slice) * sampleSize;
			Vector3 origin = b.center - b.extents;

			for (int x = 0; x < sampleCount; ++x)
			for (int y = 0; y < sampleCount; ++y)
			{
				Vector3 offset = Vector3.zero;
				switch(axis){
					case Axis.X: offset = new Vector3(sweep,y * sampleSize,x * sampleSize); break;
					case Axis.Y: offset = new Vector3(x * sampleSize,sweep,y * sampleSize); break;
					case Axis.Z: offset = new Vector3(x * sampleSize,y * sampleSize,sweep); break;
				}

				Vector4 position = origin + offset;
				float distance = Oni.SampleDistanceField(field.OniDistanceField,position.x,position.y,position.z);
				
				float value = ObiUtils.Remap(distance,-maxDistance,maxDistance,0,1);

				cutawayTexture.SetPixel(x,y,new Color(value,0,0));
			}
			cutawayTexture.Apply();
		}

		private void DrawCutawayPlane(ObiDistanceField field, Matrix4x4 matrix){
				
			if (field == null)
				return;

			RefreshCutawayTexture(field);

			material.mainTexture = cutawayTexture;
			material.SetPass(0);

			Quaternion rotation = Quaternion.identity;
			Vector3 offset = Vector3.zero;
			offset[(int)axis] = field.FieldBounds.size[0];

			if (axis == Axis.Y)
				rotation = Quaternion.Euler(90,0,0);
			else if (axis == Axis.X)
				rotation = Quaternion.Euler(0,-90,0);

			Matrix4x4 sc = Matrix4x4.TRS(field.FieldBounds.center + offset*(slice-0.5f),rotation,Vector3.one*field.FieldBounds.size[0]);
			Graphics.DrawMeshNow(planeMesh,matrix*sc);

		}

		public void OnDrawGizmos(){
			if (unityCollider != null && unityCollider.distanceField != null && unityCollider.distanceField.Initialized && material != null){
				DrawCutawayPlane(unityCollider.distanceField,transform.localToWorldMatrix);
				Gizmos.color = boundsColor;
				Gizmos.DrawWireCube(unityCollider.distanceField.FieldBounds.center,unityCollider.distanceField.FieldBounds.size);
			}
		}

	}
}

